package com.junmeng.libadapter.decoration;

import android.graphics.Canvas;
import android.graphics.Rect;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import androidx.annotation.CallSuper;
import androidx.annotation.LayoutRes;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

/**
 * 可以用xml布局实现ItemDecoration的绘制，这对于复杂布局或以后需要调整的布局或布局内容变动频繁的非常有用
 * 内部提供了按顺序计算绘制区域的方法
 */
public abstract class BaseLayoutOrderItemDecoration extends RecyclerView.ItemDecoration {

    public View mItemView;//布局的view对象

    private int mItemHeight;//布局的高度
    private int mItemWidth;//布局的宽度

    private int mParentRestMeasureWidth;//父view剩余宽度，用于测量
    private int mParentRestMeasureHeight;//父view的剩余高度，用于测量

    public BaseLayoutOrderItemDecoration() {
    }

    /**
     * 用户需要在此返回想要绘制的布局资源id
     * 注意，此布局的根布局的margin是无效的
     *
     * @return
     */
    @LayoutRes
    public abstract int getItemDecorationLayoutResId();

    /**
     * 用户根据自身需要在此处对布局进行初始化（即用户根据需要在这里进行findViewById）
     *
     * @param itemView 即getItemDecorationLayoutResId所对应的view对象
     */
    public abstract void initItemDecorationLayoutViews(@NonNull View itemView);

    /**
     * 注意不能在此处设置view的内容，这是由于getCurrentOrderDecorationDistance会调用此方法，因此在此处设置view的内容会覆盖用户实际上想要的内容
     *
     * @param outRect
     * @param view
     * @param parent
     * @param state
     */
    @CallSuper
    @Override
    public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);
        if (mItemView == null) {
            mItemView = LayoutInflater.from(parent.getContext()).inflate(getItemDecorationLayoutResId(), parent, false);
            initItemDecorationLayoutViews(mItemView);
            setCurrentItemDecorationRestMeasureWidth(getRestMeasureWidth(parent));
            setCurrentItemDecorationRestMeasureHeight(getRestMeasureHeight(parent));
        }
    }

    //-------------------------开放如下方法---------------------------------------------

    /**
     * 获得整个child装饰view的左上角坐标和右下角坐标(是多个itemdecoration合起来的区域)
     *
     * @param layoutManager
     * @param child
     * @return
     */
    public Rect getDecoratedBoundsWithMargins(RecyclerView.LayoutManager layoutManager, View child) {
        Rect rect = new Rect();
        layoutManager.getDecoratedBoundsWithMargins(child, rect);
        return rect;
    }


    /**
     * 获得当前ItemDecoration及其之前的ItemDecoration作用后的绘制区域坐标，与RecyclerView.LayoutManager.getDecoratedBoundsWithMargins相类似
     * 只不过它只包含自己及在它之前添加的ItemDecoration，例如recyclerview共添加了3个ItemDecoration,假如当前ItemDecoration是第二个添加的，
     * 那么getCurrentOrderDecoratedBoundsWithMargins获得的就是包含第一个和第二个ItemDecoration的区域坐标,
     * 假如当前ItemDecoration是第三个添加的，那么getCurrentOrderDecoratedBoundsWithMargins获得的就是包含第一个，第二个和第三个ItemDecoration的区域坐标
     * <p>
     * 注意与ItemDecoration的添加顺序有关,另外切不可在getItemOffsets中调用此方法，避免死循环
     *
     * @param child
     * @param parent
     * @param state
     * @return
     */
    public Rect getCurrentOrderDecoratedBoundsWithMargins(@NonNull View child, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) child.getLayoutParams();
        //获得child的区域坐标
        int left = child.getLeft() - lp.getMarginStart();
        int top = child.getTop() - lp.topMargin;
        int right = child.getRight() + lp.getMarginEnd();
        int bottom = child.getBottom() + lp.bottomMargin;
        Rect childPosition = new Rect(left, top, right, bottom);

        Rect distanceRect = getCurrentOrderDecorationDistance(child, parent, state);

        childPosition.left -= distanceRect.left;
        childPosition.top -= distanceRect.top;
        childPosition.right += distanceRect.right;
        childPosition.bottom += distanceRect.bottom;

        return childPosition;
    }

    /**
     * 获得当前ItemDecoration及其之前的ItemDecoration作用后与child的上下左右距离,与RecyclerView.LayoutManager.calculateItemDecorationsForChild类似
     * 注意，这与itemDecoration的添加顺序有关，所得到的结果是当前ItemDecoration及在当前ItemDecoration之前添加的ItemDecoration的综合，
     * 并不包含当前ItemDecoration之后添加的ItemDecoration
     * 注意与ItemDecoration的添加顺序有关,另外切不可在getItemOffsets中调用此方法，避免死循环
     *
     * @param child
     * @param parent
     * @param state
     * @return
     */
    public Rect getCurrentOrderDecorationDistance(@NonNull View child, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        Rect rect = new Rect();
        int itemDecorCount = parent.getItemDecorationCount();
        Rect tmp = new Rect();
        for (int i = 0; i < itemDecorCount; i++) {
            RecyclerView.ItemDecoration id = parent.getItemDecorationAt(i);
            tmp.setEmpty();
            id.getItemOffsets(tmp, child, parent, state);
            rect.left += tmp.left;//加上偏移以得到正确的绘制坐标
            rect.top += tmp.top;//加上偏移以得到正确的绘制坐标
            rect.right += tmp.right;//加上偏移以得到正确的绘制坐标
            rect.bottom += tmp.bottom;//加上偏移以得到正确的绘制坐标
            if (id == this) {//如果是自己则可以退出循环了
                break;
            }
        }
        return rect;
    }

    /**
     * 获得当前ItemDecoration及其之前的ItemDecoration作用后的绘制区域的高度（包含child的margin）
     * 注意与ItemDecoration的添加顺序有关,另外切不可在getItemOffsets中调用此方法，避免死循环
     *
     * @param child
     * @param parent
     * @param state
     * @return
     */
    public int getCurrentOrderDecoratedMeasuredHeight(@NonNull View child, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        Rect distanceRect = getCurrentOrderDecorationDistance(child, parent, state);
        ViewGroup.MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams) child.getLayoutParams();
        return child.getMeasuredHeight() + mlp.topMargin + mlp.bottomMargin + distanceRect.top + distanceRect.bottom;
    }

    /**
     * 获得当前ItemDecoration及其之前的ItemDecoration作用后的绘制区域的宽度（包含child的margin）
     * 注意与ItemDecoration的添加顺序有关,另外切不可在getItemOffsets中调用此方法，避免死循环
     *
     * @param child
     * @param parent
     * @param state
     * @return
     */
    public int getCurrentOrderDecoratedMeasuredWidth(@NonNull View child, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        Rect distanceRect = getCurrentOrderDecorationDistance(child, parent, state);
        ViewGroup.MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams) child.getLayoutParams();
        return child.getMeasuredWidth() + mlp.getMarginStart() + mlp.getMarginEnd() + distanceRect.left + distanceRect.right;
    }


    /**
     * 获得指定view的剩余宽度（即可供子View的最大宽度）
     *
     * @param view 此view必须是已经测量过的，否则返回值并不准确
     * @return
     */
    public int getRestMeasureWidth(@NonNull View view) {
        return view.getMeasuredWidth() - view.getPaddingEnd() - view.getPaddingStart();
    }

    /**
     * 获得指定view的剩余高度（即可供子View的最大高度）
     *
     * @param view 此view必须是已经测量过的，否则返回值并不准确
     * @return
     */
    public int getRestMeasureHeight(@NonNull View view) {
        return view.getMeasuredHeight() - view.getPaddingTop() - view.getPaddingBottom();
    }

    /**
     * 如果recyclerview的itemview宽度不固定或者与itemview宽度不一致,则需要在ondraw中调用此进行更新
     * 实例：
     * public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
     * int count = parent.getChildCount();
     * for (int i = 0; i < count; i++) {
     * View child = parent.getChildAt(i);
     * setCurrentItemDecorationRestMeasureWidth(getCurrentDecoratedMeasuredWidth(child,parent,state));
     * setCurrentItemDecorationRestMeasureHeight(getCurrentDecoratedMeasuredHeight(child,parent,state));
     * // ...
     * }
     * }
     *
     * @param width
     */
    public void setCurrentItemDecorationRestMeasureWidth(int width) {
        mParentRestMeasureWidth = width;
    }

    /**
     * 同{@link BaseLayoutOrderItemDecoration#setCurrentItemDecorationRestMeasureWidth}
     *
     * @param height
     */
    public void setCurrentItemDecorationRestMeasureHeight(int height) {
        mParentRestMeasureHeight = height;
    }

    /**
     * 获得ItemDecoration布局view对象
     *
     * @return
     */
    public View getItemDecorationView() {
        return mItemView;
    }

    /**
     * 获得ItemDecoration布局的高度
     * 内部自动调用notifyItemLayoutContentChange
     * 注意notifyItemLayoutContentChange可能会触发测量，因此不能同时多次调用 getItemDecorationHeight，否则可能会导致性能稍微下降
     *
     * @return
     */
    public int getItemDecorationHeight() {
        notifyItemDecorationChanged();
        return mItemHeight;
    }

    /**
     * 获得ItemDecoration布局的宽度
     * 内部自动调用notifyItemLayoutContentChange
     * 注意notifyItemLayoutContentChange可能会触发测量，因此不能同时多次调用 getItemDecorationWidth，否则可能会导致性能稍微下降
     *
     * @return
     */
    public int getItemDecorationWidth() {
        notifyItemDecorationChanged();
        return mItemWidth;
    }

    /**
     * 用户每次更新layout的内容都必须调用此方法
     */
    public void notifyItemDecorationChanged() {
        measureAndLayout();
    }

    /**
     * 对view进行测量
     *
     * @param view
     * @param restWidth  view的父剩余宽度
     * @param restHeight view的父剩余高度
     */
    public void measure(@NonNull View view, int restWidth, int restHeight) {
        int widthMeasureMode = View.MeasureSpec.EXACTLY;
        int heightMeasureMode = View.MeasureSpec.EXACTLY;
        ViewGroup.LayoutParams lp = view.getLayoutParams();
        if (lp == null) {
            view.measure(View.MeasureSpec.makeMeasureSpec(restWidth, widthMeasureMode),
                    View.MeasureSpec.makeMeasureSpec(restHeight, heightMeasureMode));
            return;
        }
        if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT) {//WRAP_CONTENT需要AT_MOST的测量模式
            widthMeasureMode = View.MeasureSpec.AT_MOST;
        } else if (lp.width > 0) {
            restWidth = Math.min(lp.width, restWidth);
        }
        if (lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) {//WRAP_CONTENT需要AT_MOST的测量模式
            heightMeasureMode = View.MeasureSpec.AT_MOST;
        } else if (lp.height > 0) {
            restHeight = Math.min(lp.height, restHeight);
        }
        view.measure(View.MeasureSpec.makeMeasureSpec(restWidth, widthMeasureMode),
                View.MeasureSpec.makeMeasureSpec(restHeight, heightMeasureMode));
    }

    /**
     * 绘制左部或顶部的Decoration视图
     *
     * @param canvas
     * @param child
     * @param parent
     * @param state
     */
    public void drawDecorationViewOnLeftOrTop(@NonNull Canvas canvas, @NonNull View child, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        canvas.save();
        Rect rect = getCurrentOrderDecoratedBoundsWithMargins(child, parent, state);
        canvas.translate(rect.left, rect.top);
        getItemDecorationView().draw(canvas);
        canvas.restore();
    }

    /**
     * 绘制右部的Decoration视图
     *
     * @param canvas
     * @param child
     * @param parent
     * @param state
     */
    public void drawDecorationViewOnRight(@NonNull Canvas canvas, @NonNull View child, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        canvas.save();
        Rect rect = getCurrentOrderDecoratedBoundsWithMargins(child, parent, state);
        canvas.translate(rect.right - getItemDecorationWidth(), rect.top);
        getItemDecorationView().draw(canvas);
        canvas.restore();
    }

    /**
     * 绘制底部的Decoration视图
     *
     * @param canvas
     * @param child
     * @param parent
     * @param state
     */
    public void drawDecorationViewOnBottom(@NonNull Canvas canvas, @NonNull View child, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        canvas.save();
        Rect rect = getCurrentOrderDecoratedBoundsWithMargins(child, parent, state);
        canvas.translate(rect.left, rect.bottom - getItemDecorationHeight());
        getItemDecorationView().draw(canvas);
        canvas.restore();
    }

    //-------------------------------------------私有方法--------------------------------------

    private void measureAndLayout() {
        measure(mItemView, mParentRestMeasureWidth, mParentRestMeasureHeight);
        mItemHeight = mItemView.getMeasuredHeight();
        mItemWidth = mItemView.getMeasuredWidth();
        mItemView.layout(0, 0, mItemView.getMeasuredWidth(), mItemView.getMeasuredHeight());
    }
}